mod config;
mod handlers;
mod middlewares;
mod models;
mod openapi;
use anyhow::Context;
use axum::{
    middleware::from_fn_with_state,
    routing::{delete, get, post},
    Router,
};
use chat_core::auth::verify_token;
pub use chat_core::jwt::DecodingKey;
use chat_core::set_layer;
pub use chat_core::User;
use chat_core::{error, jwt::EncodingKey, TokenVerify};
pub use config::AppConfig;
use error::AppError;
pub use handlers::*;
use middlewares::verify_chat;
pub use models::ChatFile;
pub use models::*;
use openapi::OpenApiRouter;
use sqlx::PgPool;
use std::{ops::Deref, sync::Arc};
use tokio::fs;
#[derive(Debug, Clone)]
pub(crate) struct AppState {
    pub(crate) inner: Arc<AppStateInner>,
}
#[allow(dead_code)]
pub(crate) struct AppStateInner {
    pub(crate) config: AppConfig,
    pub(crate) dk: DecodingKey,
    pub(crate) ek: EncodingKey,
    pub(crate) pool: sqlx::PgPool,
}
pub async fn get_router(config: AppConfig) -> Result<Router, AppError> {
    let app_state = AppState::try_new(config).await?;
    let chat = Router::new()
        .route(
            "/:id",
            delete(delete_chat_handler)
                .get(get_chat_handler)
                .post(send_message_handler),
        )
        .route("/:id/messages", get(list_message_handler))
        .layer(from_fn_with_state(app_state.clone(), verify_chat))
        .route(
            "/",
            get(list_chat_handler)
                .post(create_chat_handler)
                .patch(update_chat_handler),
        );

    let api = Router::new()
        .route("/users", get(list_chat_users_handler))
        .nest("/chats", chat)
        .route("/upload", post(upload_handler))
        .route("/files/:ws_id/*path", get(file_handler))
        .layer(from_fn_with_state(
            app_state.clone(),
            verify_token::<AppState>,
        ))
        .route("/signin", post(signin_handler))
        .route("/signup", post(signup_handler));
    let app = Router::new()
        .openapi()
        .route("/", get(index_handler))
        .nest("/api", api)
        .with_state(app_state);
    Ok(set_layer(app))
}

pub fn health() -> String {
    "".to_string()
}
impl AppState {
    pub async fn try_new(config: AppConfig) -> Result<Self, AppError> {
        fs::create_dir_all(&config.server.base_dir)
            .await
            .context("failed to create base dir")?;
        let ek = EncodingKey::load(&config.auth.sk).context("failed to load secret key")?;
        let dk = DecodingKey::load(&config.auth.pk).context("failed to load public key")?;
        let pool = PgPool::connect(&config.server.db_url)
            .await
            .context("failed to connect to database")?;
        Ok(Self {
            inner: Arc::new(AppStateInner {
                config,
                dk,
                ek,
                pool,
            }),
        })
    }
}
impl Deref for AppState {
    type Target = AppStateInner;
    fn deref(&self) -> &Self::Target {
        &self.inner
    }
}
impl TokenVerify for AppState {
    type Error = AppError;
    fn verify(&self, token: &str) -> Result<User, Self::Error> {
        self.dk.verify(token)
    }
}
// 为AppStateInner 实现debug 方法
impl std::fmt::Debug for AppStateInner {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("AppStateInner")
            .field("config", &self.config)
            .finish()
    }
}

#[cfg(test)]
mod test_utils {
    use super::*;
    use anyhow::Result;
    use sqlx::PgPool;
    impl AppState {
        pub async fn new_for_test() -> Result<Self, AppError> {
            let cfg = AppConfig::load()?;
            let dk = DecodingKey::load(&cfg.auth.pk).context(" load pk failed")?;
            let sk = EncodingKey::load(&cfg.auth.sk).context(" load sk failed")?;
            let pool = PgPool::connect(&cfg.server.db_url)
                .await
                .context("failed to connect to database")?;
            let state = Self {
                inner: Arc::new(AppStateInner {
                    config: cfg,
                    dk,
                    ek: sk,
                    pool: pool.clone(),
                }),
            };
            Ok(state)
        }
    }
}
